/***************************************************************************************
* Copyright (c) 2014-2022 Zihao Yu, Nanjing University
*
* NEMU is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

#include <isa.h>
#include <cpu/cpu.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <ftrace.h>
#include "sdb.h"
#include <memory/vaddr.h>
#include "snapshot.h"

static int is_batch_mode = false;

void init_regex();
void init_wp_pool();

/* We use the `readline' library to provide more flexibility to read from stdin. */
static char* rl_gets() {
    static char* line_read = NULL;

    if (line_read) {
        free(line_read);
        line_read = NULL;
    }

    line_read = readline("(nemu) ");

    if (line_read && *line_read) {
        add_history(line_read);
    }

    return line_read;
}

static int cmd_c(char* args) {
    cpu_exec(-1);
    return 0;
}

static int cmd_q(char* args) {
    nemu_state.state = NEMU_QUIT;
    return -1;
}

static int cmd_si(char* args) {
    int step = 1;
    char* cur = strtok(NULL, " ");
    if (cur != NULL) {
        step = atoi(cur);
    }
    cpu_exec(step);
    return 0;
}

static int cmd_info(char* args) {
    char* cur = strtok(NULL, " ");
    if (cur == NULL) return 0;

    if (strcmp(cur, "r") == 0) {
        isa_reg_display();
        return 0;
    }
#ifdef CONFIG_WATCHPOINT
    if (strcmp(cur, "w") == 0) {
        void list_wp();
        list_wp();
        return 0;
    }
#endif
#ifdef CONFIG_FTRACE
    if (strcmp(cur, "f") == 0) {
        ftrace_print();
        return 0;
    }
#endif
    return 0;
}

static int cmd_p(char* args) {
    if (args == NULL) return 0;
    bool success;
    word_t val = expr(args, &success);
    // Need to optimize !!!!!
    if (success) printf("hex value: " FMT_WORD ", dec value: %lu\n", val, (uint64_t)val);
    else         printf("bad expr: %s\n", args);
    return 0;
}

static int cmd_x(char* args) {
    char* cur = strtok(NULL, " ");
    if (cur == NULL) return 0;
    
    int N = atoi(cur);
    
    bool success;
    vaddr_t base_addr = expr(cur + strlen(cur) + 1, &success);
    if (!success) {
        printf("bad expr: %s\n", cur);
        return 0;
    }
    int word_size = sizeof(word_t);
    for (int i = 0; i < N; ++i) {
        word_t data = vaddr_read(base_addr + i * word_size, word_size);
        printf(FMT_WORD ": ", (base_addr + i * word_size));
        for (int j = 0; j < word_size; ++j) {
            printf("0x%02x  ", (uint8_t) (data&0xFF));
            data >>= 8;
        }
        printf("\n");
    }
    return 0;
}

static int cmd_w(char* args) {
#ifdef CONFIG_WATCHPOINT
    if (args == NULL) {
        return 0;
    }
    int set_wp(char* e);
    int NO = set_wp(args);
    if (NO == -1) { 
        printf("Bad expr!\n");
        return 0;
    }
    printf("Set watchpoint [%d]\n", NO);
#endif
    return 0;
}

static int cmd_d(char* args) {
#ifdef CONFIG_WATCHPOINT
    if (args == NULL) {
        return 0;
    }
    int NO = atoi(args);
    bool del_wp(int NO);
    if (del_wp(NO)) printf("Delete watchpoint [%d]\n", NO);
#endif
    return 0;
}

static int cmd_detach(char* args) {
#ifdef CONFIG_DIFFTEST
    void difftest_detach();
    difftest_detach();
#endif
    return 0;
}

static int cmd_attach(char* args) {
#ifdef CONFIG_DIFFTEST
    void difftest_attach();
    difftest_attach();
#endif
    return 0;
}

static int cmd_store(char* args) {
    return store_snapshot(args);
}

static int cmd_load(char* args) {
    return load_snapshot(args);
}

static int cmd_help(char* args);

static struct {
    const char* name;
    const char* description;
    int (*handler) (char* );
} cmd_table [] = {
    { "help", "Display information about all supported commands", cmd_help },
    { "c", "Continue the execution of the program", cmd_c },
    { "q", "Exit NEMU", cmd_q },
    { "si", "Step into", cmd_si },
    { "info", "Print information [r|w|f]", cmd_info},
    { "x", "Examine memory", cmd_x },
    { "p", "Print the value of expression", cmd_p },
    { "w", "Set watchpoint", cmd_w },
    { "attach", "attach", cmd_attach },
    { "detach", "detach", cmd_detach },
    { "store", "store", cmd_store },
    { "load", "load", cmd_load },
    { "d", "Delete watchpoint", cmd_d },
    
};

#define NR_CMD ARRLEN(cmd_table)

static int cmd_help(char* args) {
    /* extract the first argument */
    char* arg = strtok(NULL, " ");
    int i;

    if (arg == NULL) {
        /* no argument given */
        for (i = 0; i < NR_CMD; i ++) {
            printf("%s - %s\n", cmd_table[i].name, cmd_table[i].description);
        }
    }
    else {
        for (i = 0; i < NR_CMD; i ++) {
            if (strcmp(arg, cmd_table[i].name) == 0) {
                printf("%s - %s\n", cmd_table[i].name, cmd_table[i].description);
                return 0;
            }
        }
        printf("Unknown command '%s'\n", arg);
    }
    return 0;
}

void sdb_set_batch_mode() {
    is_batch_mode = true;
}

void sdb_mainloop() {
    if (is_batch_mode) {
        cmd_c(NULL);
        return;
    }

    for (char* str; (str = rl_gets()) != NULL; ) {
        char* str_end = str + strlen(str);

        /* extract the first token as the command */
        char* cmd = strtok(str, " ");
        if (cmd == NULL) { continue; }

        /* treat the remaining string as the arguments,
         * which may need further parsing
         */
        char* args = cmd + strlen(cmd) + 1;
        if (args >= str_end) {
            args = NULL;
        }

#ifdef CONFIG_DEVICE
        extern void sdl_clear_event_queue();
        sdl_clear_event_queue();
#endif

        int i;
        for (i = 0; i < NR_CMD; i ++) {
            if (strcmp(cmd, cmd_table[i].name) == 0) {
                if (cmd_table[i].handler(args) < 0) { return; }
                break;
            }
        }

        if (i == NR_CMD) { printf("Unknown command '%s'\n", cmd); }
    }
}

void init_sdb() {
    /* Compile the regular expressions. */
    init_regex();

    /* Initialize the watchpoint pool. */
    init_wp_pool();
}
